Customizing Views
With View, you can either customize existing views or create entirely new views.
Creating new views
As an example, we will create a text view that prepends a string to whatever text it displays.
Creating the view
You can create a new view by either subclassing the base class View or any of its subclasses.
When creating views it is preferable to use a set of view property delegates that make the development of views much easier and faster.
Please refer to the view property delegates documentation for more information.
import view.core.views.display.TextView
import view.core.views.propertyDelegates.ViewProperty
import view.utils.validators.Validator
import view.utils.validators.conditions.StringConditions
class PrependedTextView: TextView() {
var prependedText by ViewProperty("Original Text: ", Validator(StringConditions.PRESENT))
}
Creating the view builder
A View Builder takes a map of key-value pairs and uses it to build the view. The map is parsed from a serialized format such as JSON or is built by the DSL.
View Builders are "map-based" classes. Please see the utils.mapBased package that contains utility classes that help with common operations.
import org.kodein.di.Kodein
import org.kodein.di.erased.bind
import org.kodein.di.erased.provider
import view.core.loaders.builders.ViewBuilder
import view.core.loaders.builders.display.TextViewBuilder
import view.utils.extensions.nonNull
import view.utils.mapBased.keys.delegates.nullable.StringRWKey
import view.di.KodeinContainer
class PrependedTextViewBuilder: TextViewBuilder() {
override val view = PrependedTextView()
var prependedText by StringRWKey
override fun beforeProduction() {
super.beforeProduction()
prependedText.nonNull { view.prependedText = it }
}
}
// add DI bindings to the library's bindings container
val newBindings = Kodein {
bind<ViewBuilder<*>>("PrependedTextView") with provider { PrependedTextViewBuilder() }
}
KodeinContainer.addConfig(newBindings)
Creating the DSL
This step is optional.
object prependedTextView {
operator fun invoke(init: PrependedTextViewBuilder.() -> Unit): PrependedTextView {
return PrependedTextViewBuilder().apply {
init()
}.build() as PrependedTextView
}
}
// can be used as follows
val pTV = prependedTextView {
prependedText = "This is text will be prepended"
}
Adding a renderer
Please follow each renderer's documentation on how to add renderers for new views.
Creating a custom view property delegate
View property delegates are helpful as they allow us to control how view properties are set and retrieved. This package contains multiple delegates that can be used.
The main functions of view property delegates are: - Validate property values - Notify the renderer whenever the property's value changes
Currently, four property delegate classes are available:
1. AbstractViewProperty: Base class for mutable view property delegates
2. ViewProperty: Property delegate for non-nullable view properties
3. NullableViewProperty: Property delegate for non-nullable view properties
4. LateInitVal: Allows an immutable property to be initialized later in the class' lifetime.
As an example, we will create a property delegate that logs to the console whenever a property is accessed.
import view.core.views.View
import view.core.views.propertyDelegates.ViewProperty
import view.utils.validators.Validator
import kotlin.reflect.KProperty
class LoggerProperty<T: Any>(
initValue: T,
validator: Validator<T>? = null
): ViewProperty<T>(initValue, validator) {
override fun getValue(thisRef: View, property: KProperty<*>): T {
val value = super.getValue(thisRef, property)
println("Retrieving value $value from property ${property.name}")
return value
}
override fun setValue(thisRef: View, property: KProperty<*>, value: T) {
super.setValue(thisRef, property, value)
println("Set property ${property.name} with value $value")
}
}
// can be used as follows
import view.utils.validators.conditions.IntegerConditions
val counter by LoggerProperty(0, Validator(IntegerConditions.NON_NEGATIVE))